---
sidebar_position: 2.5
---

# Custom Tree Data Provider


In more complex scenarios, it's probably easiest to implement your own data provider.
This provider must implement the [TreeDataProvider interface](/docs/api/interfaces/TreeDataProvider), i.e.

```typescript
export interface TreeDataProvider<T = any> {
  onDidChangeTreeData?: (listener: (changedItemIds: TreeItemIndex[]) => void) => Disposable;
  getTreeItem: (itemId: TreeItemIndex) => Promise<TreeItem<T>>;
  getTreeItems?: (itemIds: TreeItemIndex[]) => Promise<TreeItem[]>;
  onRenameItem?: (item: TreeItem<T>, name: string) => Promise<void>;
  onChangeItemChildren?: (itemId: TreeItemIndex, newChildren: TreeItemIndex[]) => Promise<void>;
}
```

At least the `getTreeItem` method must be implemented, to declare how data can be made available
to the tree structure. `getTreeItems` allows you to make loading more efficient if multiple entries
need to be loaded at once. If you do not implement `getTreeItems`, they are loaded sequentially
using `getTreeItem`.

The methods `onRenameItem` and `onChangeItemChildren` allow you to declare how updates to the
tree structure should be handled, i.e. by renaming an item or moving items from one parent to
another. You still need to enable this functionality in the environment by providing the respective
flags. Look into the [TreeCapabilities interface](/docs/api/interfaces/TreeCapabilities) for more details
on the necessary flags.

You can use this implementation as baseline:

```typescript

class CustomDataProviderImplementation implements TreeDataProvider {
  private data: Record<TreeItemIndex, TreeItem> = { ...shortTree.items };

  private treeChangeListeners: ((changedItemIds: TreeItemIndex[]) => void)[] =
    [];

  public async getTreeItem(itemId: TreeItemIndex) {
    return this.data[itemId];
  }

  public async onChangeItemChildren(
    itemId: TreeItemIndex,
    newChildren: TreeItemIndex[]
  ) {
    this.data[itemId].children = newChildren;
    this.treeChangeListeners.forEach(listener => listener([itemId]));
  }

  public onDidChangeTreeData(
    listener: (changedItemIds: TreeItemIndex[]) => void
  ): Disposable {
    this.treeChangeListeners.push(listener);
    return {
      dispose: () =>
        this.treeChangeListeners.splice(
          this.treeChangeListeners.indexOf(listener),
          1
        ),
    };
  }

  public async onRenameItem(item: TreeItem<any>, name: string): Promise<void> {
    this.data[item.index].data = name;
  }

  // custom handler for directly manipulating the tree data
  public injectItem(name: string) {
    const rand = `${Math.random()}`;
    this.data[rand] = { data: name, index: rand } as TreeItem;
    this.data.root.children?.push(rand);
    this.treeChangeListeners.forEach(listener => listener(['root']));
  }
}
```

## Reacting to Drag Events

RCT will call `onChangeItemChildren` when a drag operation is finished. You can use this directly
to update your data source. Note that, if you add or remove items, the affected item
is the parent item, not the added or removed items.

In the exemplary implementation above, this emits an event on the `treeChangeListeners` listeners,
where you could register a custom listener to react to changes.

## Reacting to Rename Events

RCT will call `onRenameItem` when a rename operation is finished. Implement your rename logic there.

## Custom Provider Live Demo

```jsx live
function App() {
  const dataProvider = useMemo(
    () => {
      class CustomDataProviderImplementation {
        data = { ...shortTree.items };

        treeChangeListeners = [];

        async getTreeItem(itemId) {
          return this.data[itemId];
        }

        async onChangeItemChildren(itemId, newChildren) {
          this.data[itemId].children = newChildren;
          this.treeChangeListeners.forEach(listener => listener([itemId]));
        }

        onDidChangeTreeData(listener) {
          this.treeChangeListeners.push(listener);
          return {
            dispose: () =>
              this.treeChangeListeners.splice(
                this.treeChangeListeners.indexOf(listener),
                1
              ),
          };
        }

        async onRenameItem(item, name) {
          this.data[item.index].data = name;
        }

        injectItem(name) {
          const rand = `${Math.random()}`;
          this.data[rand] = { data: name, index: rand };
          this.data.root.children.push(rand);
          this.treeChangeListeners.forEach(listener => listener(['root']));
        }
      }
      return new CustomDataProviderImplementation()
    },
    []
  );

  return (
    <UncontrolledTreeEnvironment
      canDragAndDrop
      canDropOnFolder
      canReorderItems
      dataProvider={dataProvider}
      getItemTitle={item => item.data}
      viewState={{
        'tree-2': {
          expandedItems: [],
        },
      }}
    >
      <button
        type="button"
        onClick={() =>
          dataProvider.injectItem(window.prompt('Item name') || 'New item')
        }
      >
        Inject item
      </button>
      <Tree treeId="tree-2" rootItem="root" treeLabel="Tree Example" />
    </UncontrolledTreeEnvironment>
  );
}
```